﻿using System;
using System.Collections.Generic;
using NHibernate;
using NHibernate.Criterion;

namespace WSKnowledge.WSHelpers.NHibernate
{
	public interface IRepository<TEntity>
		where TEntity : IEntity<TEntity>
	{
		IUnitOfWork UnitOfWork { get; }
		Type ConcreteType { get; }

		void Persist(TEntity entity);
		void Delete(TEntity entity);
		TEntity Get(object id);
		TEntity Get(object id, LockMode lockMode);
		IList<TEntity> GetAll();
		void DeleteAll();

		#region FindOne overloads

		TEntity FindOne(ICriterion criterion);
		TEntity FindOne(ICriterion[] criterions);

		#endregion

		#region FindAll overloads

		IList<TEntity> FindAll(ICriterion criterion);
		IList<TEntity> FindAll(ICriterion criterion, Order order);
		IList<TEntity> FindAll(ICriterion criterion, Order[] orders);
		IList<TEntity> FindAll(ICriterion[] criterions);
		IList<TEntity> FindAll(ICriterion[] criterions, Order order);
		IList<TEntity> FindAll(ICriterion[] criterions, Order[] orders);

		#endregion
	}

	public class NHRepository<TEntity> : IRepository<TEntity>
		where TEntity : IEntity<TEntity>
	{
		private readonly IUnitOfWork _unit;
		private static readonly Order[] EmptyOrdersArray = new Order[] { };

		public NHRepository(IUnitOfWork unit)
		{
			_unit = unit;
		}

		#region IRepository Members

		public IUnitOfWork UnitOfWork
		{
			get { return _unit; }
		}

		public ISession Session { get { return _unit.GetSession(); } }

		public Type ConcreteType { get { return typeof(TEntity); } }

		public void Persist(TEntity entity)
		{
			if (!entity.Validate())
				throw new UnitOfWorkException("Can not persist this entity. Validation result is false.");

			if (!_unit.IsInTransaction)
				_unit.BeginTransaction();

			Session.SaveOrUpdate(entity);
		}

		public void Delete(TEntity entity)
		{
			if (!_unit.IsInTransaction)
				_unit.BeginTransaction();

			Session.Delete(entity);
		}

		public TEntity Get(object id)
		{
			return Session.Get<TEntity>(id);
		}

		public TEntity Get(object id, LockMode lockMode)
		{
			return Session.Get<TEntity>(id, lockMode);
		}

		public IList<TEntity> GetAll()
		{
			return Session.CreateCriteria(ConcreteType)
				.SetCacheable(true)
				.List<TEntity>();
		}

		public void DeleteAll()
		{
			var entities = GetAll();
			foreach (var entity in entities)
				Delete(entity);
		}

		private ICriteria GetBasicCriteria()
		{
			return Session.CreateCriteria(ConcreteType, ConcreteType.Name.ToLower());
		}

		#region FindOne overloads

		public TEntity FindOne(ICriterion criterion)
		{
			return FindOne(new[] { criterion });
		}

		public TEntity FindOne(ICriterion[] criterions)
		{
			var criteria = Session.CreateCriteria(ConcreteType, ConcreteType.Name.ToLower());

			foreach (var criterion in criterions)
				criteria.Add(criterion);

			return criteria.UniqueResult<TEntity>();
		}

		#endregion

		#region FindAll overloads

		public IList<TEntity> FindAll(ICriterion criterion)
		{
			return FindAll(new[] { criterion }, EmptyOrdersArray);
		}

		public IList<TEntity> FindAll(ICriterion criterion, Order order)
		{
			return FindAll(new[] { criterion }, new[] { order });
		}

		public IList<TEntity> FindAll(ICriterion criterion, Order[] orders)
		{
			return FindAll(new[] { criterion }, orders);
		}

		public IList<TEntity> FindAll(ICriterion[] criterions)
		{
			return FindAll(criterions, EmptyOrdersArray);
		}

		public IList<TEntity> FindAll(ICriterion[] criterions, Order order)
		{
			return FindAll(criterions, new[] { order });
		}

		public IList<TEntity> FindAll(ICriterion[] criterions, Order[] orders)
		{
			ICriteria criteria = GetBasicCriteria();

			foreach (ICriterion criterion in criterions)
				criteria.Add(criterion);

			foreach (Order order in orders)
				criteria.AddOrder(order);

			return criteria.List<TEntity>();
		}

		#endregion

		#endregion
	}
}
